﻿//  Copyright (c) Microsoft Corporation.  All Rights Reserved.

using System;
using System.CodeDom;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.ServiceModel.Channels;
using System.Web.Services.Description;
using System.Xml;
using System.Xml.Schema;
using System.Text;

namespace ExecutionContract
{
    public class ExecutionContractImporter :
    IWsdlImportExtension,
    IServiceContractGenerationExtension,
    IOperationContractGenerationExtension,
    IContractBehavior,
    IOperationBehavior
    {
        private XmlElement RootElement { get; set; }
        /// <summary>
        /// This is the number of parameters that are turned into the current method. Watch out, when there are 2 parameters in the input then the arguments are named with "arg0", "arg1".
        /// </summary>
        private int countOfCurrentParameters;

        private string currentServiceClient;

        private string currentAssemblyName;

        private string currentObjectName;

        private XmlNode currentProperties;
        private string currentObjectNamespace;

        private string returnType;
        #region WSDL Import
        public ExecutionContractImporter()
        {
            Debug.WriteLine("WsdlDocumentationImporter created.");
        }

        public ExecutionContractImporter(XmlElement comment)
        {
            this.RootElement = comment;
            Debug.WriteLine("WsdlDocumentationImporter created.");
        }

        public void ImportContract(WsdlImporter importer, WsdlContractConversionContext context)
        {
          
            // Contract Documentation
            //this.RootElement = context.WsdlPortType.DocumentationElement;
            if (context.WsdlPortType.Documentation != null)
            {
                // System examines the contract behaviors to see whether any implement IWsdlImportExtension.
                context.Contract.Behaviors.Add(new ExecutionContractImporter(context.WsdlPortType.DocumentationElement));
            }
            // Operation Documentation
            //foreach (Operation operation in context.WsdlPortType.Operations)
            //{
            //    if (operation.Documentation != null)
            //    {
            //        OperationDescription operationDescription = context.Contract.Operations.Find(operation.Name);
            //        if (operationDescription != null)
            //        {
            //            // System examines the operation behaviors to see whether any implement IWsdlImportExtension.
            //            operationDescription.Behaviors.Add(new ExecutionContractImporter(operation.Documentation));
            //        }
            //    }
            //}
            //    OperationDescription descr = new OperationDescription("Test",context.Contract);

            //  context.Contract.Operations.Add(descr);
        }

        public void BeforeImport(ServiceDescriptionCollection wsdlDocuments, XmlSchemaSet xmlSchemas, ICollection<XmlElement> policy)
        {
            Debug.WriteLine("BeforeImport called.");
        }

        public void ImportEndpoint(WsdlImporter importer, WsdlEndpointConversionContext context)
        {
        }

        #endregion

        #region Code Generation

        public void GenerateContract(ServiceContractGenerationContext context)
        {
            this.currentServiceClient = context.Contract.Name.Substring(1) + "Client";
            if (this.RootElement != null)
            {
                string insertedCode = ""; /* CompositeType{ public void test(){}}";*/

                XmlNode foo = this.RootElement.FirstChild;

                //WORKAROUND for SVCUtil.exe bug
                XmlDocument doc = new XmlDocument();
                doc.LoadXml(this.RootElement.InnerXml);

                foreach (XmlNode node in doc.ChildNodes[0].ChildNodes)
                {
                    insertedCode += "public partial class ";
                    this.currentAssemblyName = node.Attributes["Assembly"].InnerText;
                    this.currentObjectName = node.Attributes["Name"].InnerText;
                    this.currentObjectNamespace = node.Attributes["Namespace"].InnerText;
                    insertedCode += this.currentObjectName + "{";

                    this.currentProperties = node.ChildNodes[0];

                    foreach (XmlNode memberNode in node.ChildNodes[1].ChildNodes)
                    {
                        insertedCode += GenerateMember(memberNode);
                    }
                    insertedCode += "}";
                }

                CodeDirectiveCollection collection = new CodeDirectiveCollection();

                collection.Add(new CodeRegionDirective(CodeRegionMode.Start, "ExecutionContractInjectedCode" + System.Environment.NewLine + insertedCode));
                collection.Add(new CodeRegionDirective(CodeRegionMode.End, "ExecutionContractInjectedCode"));
                CodeTypeDeclaration decla = new CodeTypeDeclaration("Foo");


                context.ContractType.StartDirectives.AddRange(collection);

                Debug.WriteLine("In generate contract.");
                //context.ContractType.Comments.AddRange(FormatComments(text));
            }
        }

        private string GenerateMember(XmlNode memberNode)
        {
            return memberNode.Attributes["Modifier"].InnerText + " " + GetReturn(memberNode.ChildNodes[1]) + " " + memberNode.Attributes["Name"].InnerText + "(" + GenerateInput(memberNode.ChildNodes[0]) + "){" + System.Environment.NewLine + GetCode(memberNode.ChildNodes[2], memberNode.Attributes["Name"].InnerText) + System.Environment.NewLine + "}";
        }

        private string GetReturn(XmlNode returnNode)
        {
            this.returnType = returnNode.InnerText;
            return returnNode.InnerText;
        }

        private string GetCode(XmlNode xmlNode, string _methodName)
        {
            if (xmlNode.Attributes["isLocal"] == null)
            {
                string retValue = "";
                retValue += this.currentServiceClient + " client = new " + this.currentServiceClient + "();" + Environment.NewLine;
                retValue += "string endpoint = client.Endpoint.Address.Uri.AbsoluteUri;" + Environment.NewLine;
                retValue += "ExecutionContract.IExecutionContractServiceExtenderChannel channel = new System.ServiceModel.ChannelFactory<ExecutionContract.IExecutionContractServiceExtenderChannel>(new System.ServiceModel.BasicHttpBinding(), new System.ServiceModel.EndpointAddress(endpoint)).CreateChannel();" + Environment.NewLine;
                retValue += "object[] temp = {";
                //write all parameters that are generate by GenerateInput into the objet[] that is passed to the serviceclient
                bool tempUsed = false;
                for (int i = 0; i < countOfCurrentParameters; i++)
                {
                    tempUsed = true;
                    retValue += "arg" + i + " ,";
                }
                //delete the last ','
                if (tempUsed)
                {
                    retValue = retValue.Substring(0, retValue.Length - 1);
                }
                retValue += " };" + System.Environment.NewLine;
                //generate properties array
                retValue += "System.Tuple<string, object>[] prop ={";
                foreach (XmlNode propertyNode in this.currentProperties.ChildNodes)
                {
                    retValue += "new System.Tuple<string,object>(\"" + propertyNode.Attributes["Name"].InnerText + "\", this." + propertyNode.Attributes["Name"].InnerText + "),";
                }
                //delete the last ','
                retValue = retValue.Substring(0, retValue.Length - 1);
                retValue += " };" + System.Environment.NewLine;
                retValue += "Tuple<object, Tuple<string, object>[]> result = channel.Execute(\"" + this.currentAssemblyName + "\",\"" + this.currentObjectNamespace + "." + this.currentObjectName + "\", \"" + _methodName + "\",prop, temp);" + Environment.NewLine;
                retValue += "ExecutionContract.ExecutionContractClientSideHelper.EnrichObject(this, result.Item2);" + Environment.NewLine;
                retValue += "return (" + this.returnType + ") result.Item1;";

                return retValue;
            }
            else
            {
                string[] temp = xmlNode.InnerText.Split(ExecutionContractServerSideGenerator.Separators);
                
                //parse string[] to byte[]
                byte[] inArray = new byte[temp.Length];
                for (int i = 0; i < inArray.Length; i++)
                {
                    string foo = temp[i];
                    inArray[i] = byte.Parse(temp[i]);
                }

                //encode the byte[]
                return Encoding.ASCII.GetString(inArray);
            }
        }

        private string GenerateInput(XmlNode _inputNode)
        {
            int count = 0;
            string retValue = "";
            foreach (XmlNode node in _inputNode)
            {
                retValue += node.InnerText + " arg" + count + " ,";
                count++ ;
            }
            //set countOfCurrentParameters
            this.countOfCurrentParameters = count;
            //we have to delete the last ',' to avoid syntax errors
            try
            {
                retValue = retValue.Substring(0, retValue.Length - 1);
            }
            catch
            {
                //avoid zero length error, when no parameters are applied to the method
            }
            return retValue;
        }

        public void GenerateOperation(OperationContractGenerationContext context)
        {
            //context.SyncMethod.Comments.AddRange(FormatComments(text));
            Debug.WriteLine("In generate operation.");
        }

        #endregion

        #region IContractBehavior Members

        public void AddBindingParameters(ContractDescription description, ServiceEndpoint endpoint, BindingParameterCollection parameters)
        {
            return;
        }

        public void ApplyClientBehavior(ContractDescription description, ServiceEndpoint endpoint, ClientRuntime proxy)
        {
            Debug.WriteLine("Contract behavior is never called on client.");
            return;
        }

        public void ApplyDispatchBehavior(ContractDescription description, ServiceEndpoint endpoint, DispatchRuntime dispatch)
        {
            return;//this.contractDescription = description;
        }

        public void Validate(ContractDescription description, ServiceEndpoint endpoint)
        {
            return;
        }

        #endregion

        #region IOperationBehavior Members

        public void AddBindingParameters(OperationDescription description, BindingParameterCollection parameters)
        {
            return;
        }

        public void ApplyClientBehavior(OperationDescription description, ClientOperation proxy)
        {
            Debug.WriteLine("Operation behavior is never called on client.");
            return;
        }

        public void ApplyDispatchBehavior(OperationDescription description, DispatchOperation dispatch)
        {
            // this.operationDescription = description;
        }

        public void Validate(OperationDescription description)
        {
            return;
        }

        #endregion
    }
}

